package dbu.csv;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import common.Configuration;
import common.LogCategory;
import dbu.Data;
import dbu.DbFile;
import dbu.jdbc.JdbcResultSetMetaData;
import dbu.sql.Criteria;
import dbu.sql.Query;
import dbu.sql.SqlCommand;

public class CsvFile implements DbFile {

	protected static final Logger log = LogCategory.getCategory(CsvFile.class);

	private static final String CHARSET = "UTF-8";

	private static final String COMMA = ",";
	private static final String SPACE = " ";
	private static final String QUOTE = "'";

	private Configuration configuration;
	private String fileName;
	private String path;
	private String charset;
	private JdbcResultSetMetaData meta;
	private Collection<String> lines;

	public CsvFile(String tableName) {
		configuration = new Configuration("db.userspropfile", "db.properties");
		fileName = configuration.getProperties().getProperty(tableName + ".file");
		path = configuration.getProperties().getProperty("db.path", System.getProperty("user.dir"));
		charset = configuration.getProperties().getProperty("db.charset", CHARSET);
		lines = new ArrayList<String>();
		load();
	}

	public JdbcResultSetMetaData getMeta() {
		return meta;
	}

	public Data find(Query query) {
		Data data = new Data(getMeta());
		for (String line: lines) {
			addBean(data, line, query.getCriteria());
		}
		return data;
	}

	private void addBean(Data data, String line, Criteria criteria) {
		String[] bean = getBean(line);
		if ((criteria == null) || criteria.evaluate(bean, meta)) {
			data.addData(bean);
		}
	}

	public int delete(SqlCommand sqlCmd) {
		int result = 0;
		Criteria criteria = sqlCmd.getQuery().getCriteria();
		for (Iterator<String> i = lines.iterator(); i.hasNext();) {
			String line = i.next();
			String[] bean = getBean(line);
			if ((criteria == null) || criteria.evaluate(bean, meta)) {
				i.remove();
				result++;
			}
		}
		if (result > 0) {
			store();
		}
		return result;
	}

	public int update(SqlCommand sqlCmd) {
		Collection<String> updates = new ArrayList<String>(); 
		Criteria criteria = sqlCmd.getQuery().getCriteria();
		for (Iterator<String> i = lines.iterator(); i.hasNext();) {
			String line = i.next();
			String[] bean = getBean(line);
			if ((criteria == null) || criteria.evaluate(bean, meta)) {
				String update = getValues(sqlCmd);
				if (!update.equals(line)) {
					i.remove();
					updates.add(update);
				}
			}
		}
		if (updates.size() > 0) {
			lines.addAll(updates);
			store();
		}
		return updates.size();
	}

	public int insert(SqlCommand sqlCmd) {
		String line = getValues(sqlCmd);
		this.lines.add(line);
		store();
		return 1;
	}

	private String getValues(SqlCommand sqlCmd) {
		StringBuffer result = new StringBuffer();
		List<String> columns = new ArrayList<String>(sqlCmd.getColumns());
		List<String> values = new ArrayList<String>(sqlCmd.getValues());
		for (int i = 0; i < meta.getColumnCount(); i++) {
			if (result.length() > 0) {
				result.append(COMMA);
				result.append(SPACE);
			}
			int index = i;
			if (!columns.isEmpty()) {
				index = columns.indexOf(meta.getColumnName(i).toLowerCase());
			}
			String value = values.get(index);
			value = value.substring(QUOTE.length(), value.length() - QUOTE.length());
			result.append(value);
		}
		return result.toString();
	}

	private void load() {
		File file = new File(path + File.separator + fileName);
		try {
			if (!file.exists()) {
				log.warn("Missisng users file: " + file.getCanonicalPath());
				return;
			}
			LineNumberReader in = new LineNumberReader(new InputStreamReader(new FileInputStream(file), charset));
			String line = in.readLine();
			meta = new JdbcResultSetMetaData(line);
			while ( (line = in.readLine()) != null) {
				lines.add(line);
			}
			in.close();
		} catch (IOException e) {
			log.error(e.getMessage(), e);
		}
	}

	private void store() {
		/** delete old backup */
		File backup = new File(path + File.separator + fileName + ".bak");
		if (backup.exists()) {
			backup.delete();
		}
		/** rename current to backup */
		File temp = new File(path + File.separator + fileName);
		if (temp.exists()) {
			temp.renameTo(backup);
		}
		/** store data */
		File file = new File(path + File.separator + fileName);
		try {
			PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file, false), charset));
			out.println(meta);
			for (String line: lines) {
				out.println(line);
			}
			out.flush();
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private String[] getBean(String line) {
		String[] tokens = line.split(COMMA);
		String[] bean = new String[meta.getColumnCount()];
		for (int j = 0; j < tokens.length; j++) {
			if (j < meta.getColumnCount()) {
				bean[j] = tokens[j].trim();
			} else {
				/** last could contains commas */
				bean[meta.getColumnCount() - 1] += COMMA + SPACE + tokens[j].trim();
			}
		}
		return bean;
	}
}
